#include <windows.h>
#include <cassert>
#include "thread.h"
#include <interface\STaskLoop-i.h>
#include <process.h>


#ifdef _MSC_VER
// we have undocumented Win32 APIs to set thread name.
const DWORD MS_VC_EXCEPTION = 0x406D1388;
#pragma pack(push,8)
typedef struct tagTHREADNAME_INFO
{
	DWORD dwType; // Must be 0x1000.
	LPCSTR szName; // Pointer to name (in user addr space).
	DWORD dwThreadID; // Thread ID (-1=caller thread).
	DWORD dwFlags; // Reserved for future use, must be zero.
} THREADNAME_INFO;
#pragma pack(pop)
static void SetThreadName(const char *threadName)
{
	THREADNAME_INFO info;
	info.dwType = 0x1000;
	info.szName = threadName;
	info.dwThreadID = ::GetCurrentThreadId();
	info.dwFlags = 0;
#pragma warning(push)
#pragma warning(disable: 6320 6322)
	__try
	{
		RaiseException(MS_VC_EXCEPTION, 0, sizeof(info) / sizeof(ULONG_PTR), (ULONG_PTR *)&info);
	}
	__except (EXCEPTION_EXECUTE_HANDLER)
	{
	}

#pragma warning(pop)
}
#else
#include <pthread.h>
static void SetThreadName(const char *threadName)
{
	pthread_setname_np(pthread_self(), threadName);
}

#endif


namespace TASKLOOP{

class ThreadPrivate
{
public:
	ThreadPrivate():_hthread(NULL), _threadID(0)
	{

	}
	HANDLE _hthread;
	unsigned int _threadID;
};

const long Thread::INVALID_THREAD_ID = -1;

Thread::Thread() :
_stopping(false),
_runnable(NULL),
_name(),
_private(*(new ThreadPrivate())),
_start(false)
{
}

Thread::~Thread()
{
	waitForStop();
	delete &_private;
}

long Thread::getCurrentThreadID()
{
	DWORD id = ::GetCurrentThreadId();
	return (long) id;
}

bool Thread::start(SOUI::IRunnable *runnable, const std::string &name, ThreadPriority priority)
{
	assert(runnable);

	{
		SAutoLock autolock (_lock);

		if (_start)
		{
			return false;
		}

		_stopping = false;
		_runnable.Attach(runnable->clone());
		_name = name;
		_start = true;
		_priorityLevel = priority;

		_private._hthread = (HANDLE)_beginthreadex(NULL, 0, Thread::threadProcWin32thunk, this, 0, &_private._threadID);

		if (_private._hthread == NULL)
		{
			clear();
			return false;
		}
		setPriority(priority);
	}

	_startSem.wait(100);
	return true;
}

void Thread::stop()
{
	SAutoLock autolock (_lock);

	if (!_start)
	{
		return;
	}

	_stopping = true;
}

void Thread::waitForStop()
{
	// we must release lock before the wait function, to avoid deadlock
	{
		SAutoLock autolock (_lock);

		if (!_start)
		{
			if ( _private._hthread )
			{
				::CloseHandle( _private._hthread); // resource must be released
				_private._hthread = NULL;
				_private._threadID = 0;
			}

			return;
		}
	}

	DWORD ret = ::WaitForSingleObject(_private._hthread, INFINITE);

	{
		SAutoLock autolock (_lock);

		if ( WAIT_OBJECT_0 == ret )
		{
			::CloseHandle( _private._hthread);
		}

		_private._hthread = NULL;
		_private._threadID = 0;
	}

	if (WAIT_OBJECT_0  != 0)
	{
	}
}

bool Thread::isStopping()
{
	SAutoLock autolock (_lock);
	return _stopping;
}

bool Thread::isStopped()
{
	SAutoLock autolock (_lock);
	return _stopping || (!_start);
}

std::string Thread::getName()
{
	SAutoLock autolock (_lock);
	return _name;
}

void Thread::setThreadName(const std::string &name)
{
	SetThreadName(name.c_str());
}

void Thread::setPriority(ThreadPriority priorityLevel)
{
	int priority = 0;

	switch (priorityLevel)
	{
	case High:
		priority = THREAD_PRIORITY_TIME_CRITICAL;
		break;

	case Low:
		priority = THREAD_PRIORITY_IDLE;
		break;

	default:
		priority = THREAD_PRIORITY_NORMAL;
		break;
	}

	BOOL ret = SetThreadPriority(_private._hthread, priority);

	if (!ret)
	{
	}
}

void Thread::clear()
{
	SAutoLock autolock (_lock);
	_stopping = false;
	_runnable = NULL;
	_name = "";
	_start = false;
	_threadID = INVALID_THREAD_ID;
}

//  `stdcall' to 'cdecl' thunk
unsigned WINAPI Thread::threadProcWin32thunk(LPVOID lpParameter )
{
	Thread::threadProc(lpParameter);
	return 0;
}

void *Thread::threadProc(void *args)
{
	Thread *thread = static_cast<Thread *> (args);

	{
		SAutoLock autoLock(thread->_lock);
		thread->_threadID = getCurrentThreadID();
		thread->setThreadName(thread->_name);
		thread->setPriority(thread->_priorityLevel);
	}

	thread->_startSem.notify();
	thread->_runnable->run();

	{
		SAutoLock autoLock(thread->_lock);
		thread->clear();
	}

	return NULL;
}

}//end of ns